home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / sbin / update-python-modules < prev    next >
Encoding:
Text File  |  2010-06-27  |  19.2 KB  |  500 lines

  1. #! /usr/bin/python
  2. #
  3. # copyright (c) 2006 Josselin Mouette <joss@debian.org>
  4. # Licensed under the GNU Lesser General Public License, version 2.1
  5. # See COPYING for details
  6.  
  7. # Everything prefixed by old_ is compatibility code with older versions
  8. # Modules used to lie in /usr/{lib,share}/python-support/$package
  9. # They now lie in /usr/{lib,share}/pyshared
  10.  
  11. import sys,os,shutil
  12. from optparse import OptionParser
  13. from subprocess import call
  14. from py_compile import compile, PyCompileError
  15. sys.path.append("/usr/share/python-support/private/")
  16. import pysupport
  17. from pysupport import py_supported,py_installed,py_oldversions
  18.  
  19. basepath='/usr/lib/pymodules'
  20. sourcepath='/usr/share/python-support'
  21. old_extensionpath='/usr/lib/python-support'
  22. shared_path='/usr/share/pyshared'
  23. shared_extensionpath='/usr/lib/pyshared'
  24.  
  25. parser = OptionParser(usage="usage: %prog [-v] [-c] package_directory [...]\n"+
  26.                             "       %prog [-v] [-c] package.dirs [...]\n"+
  27.                             "       %prog [-v] [-a|-f|-p]")
  28. parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
  29.                   help="verbose output", default=False)
  30. parser.add_option("-c", "--clean", action="store_true", dest="clean_mode",
  31.                   help="clean modules instead of compiling them",
  32.                   default=False)
  33. parser.add_option("-a", "--rebuild-all", action="store_true",
  34.                   dest="rebuild_all", default=False,
  35.                   help="rebuild all private modules for a new default python version")
  36. parser.add_option("-f", "--force-rebuild-all", action="store_true",
  37.                   dest="rebuild_everything", default=False,
  38.                   help="rebuild all modules, including public modules for all python versions")
  39. parser.add_option("-p", "--post-install", action="store_true", dest="post_install",
  40.                   help="run post-installation operations, common to many packages",
  41.                   default=False)
  42. parser.add_option("-b", "--bytecompile", action="store_true", dest="force_private",
  43.                   help="[deprecated] byte-compilation mode: only handle private modules",
  44.                   default=False)
  45. parser.add_option("-i", "--install", action="store_true", dest="force_public",
  46.                   help="[deprecated] installation mode: only handle public modules",
  47.                   default=False)
  48. (options, args) = parser.parse_args()
  49.  
  50. def debug(x):
  51.     if(options.verbose):
  52.         print x
  53.  
  54. def warning(x):
  55.     sys.stderr.write("WARNING: %s\n"%x)
  56.  
  57. def isect(l1,l2):
  58.     return [i for i in l1 if i in l2]
  59.  
  60. def concat(l1,l2):
  61.     return l1 + [i for i in l2 if i not in l1]
  62.  
  63.  
  64. # Abstract class implementing the methods related to public modules
  65. class _PublicList (list):
  66.     pyversions = py_supported
  67.     def install (self, versions):
  68.         versions = isect (self.pyversions, versions)
  69.         for filename in self:
  70.             version = None
  71.             rng = versions
  72.             try:
  73.                 if filename.startswith (shared_path+"/"):
  74.                     # New layout, module
  75.                     relname = filename[len(shared_path)+1:]
  76.                 elif filename.startswith (shared_extensionpath+"/python"):
  77.                     # New layout, extension
  78.                     [ version, relname ] = filename[len(shared_extensionpath)+1:].split("/", 1)
  79.                 elif filename.startswith (sourcepath+"/"):
  80.                     [ package, relname ] = filename[len(sourcepath)+1:].split("/",1)
  81.                 elif filename.startswith (old_extensionpath+"/"):
  82.                     [ package, version, relname ] = filename[len(old_extensionpath)+1:].split("/",2)
  83.                 else:
  84.                     raise ValueError
  85.             except ValueError:
  86.                 warning ("%s contains an invalid filename (%s)"%(self.name, filename))
  87.                 continue
  88.             if version:
  89.                 if version not in versions:
  90.                     continue
  91.                 rng = [version]
  92.             for pyversion in rng:
  93.                 destpath = os.path.join (basepath, pyversion, relname)
  94.                 try:
  95.                     os.makedirs(os.path.dirname(destpath))
  96.                 except OSError:
  97.                     pass
  98.                 if filename[-4:] not in ['.pyc', '.pyo']:
  99.                     debug("link "+destpath)
  100.                     # os.path.exists returns False for broken symbolic links
  101.                     if os.path.exists(destpath) or os.path.islink(destpath):
  102.                         if file!="__init__.py" or (os.path.exists(destpath) and os.path.getsize(destpath)):
  103.                             # The file is already here, probably from the previous version. 
  104.                             # No need to check for conflicts, dpkg catches them earlier now
  105.                             debug("overwrite "+destpath)
  106.                         else:
  107.                             debug("overwrite namespace "+destpath)
  108.                         os.remove(destpath)
  109.                     os.symlink(filename,destpath)
  110.  
  111.  
  112. # Abstract class implementing the methods related to private modules
  113. class _PrivateList (list):
  114.     pyversion = None
  115.     def bytecompile (self):
  116.         if self.pyversion:
  117.             debug("Byte-compilation of whole %s with python%s..."%(self.name,self.pyversion))
  118.             call(['/usr/bin/python'+self.pyversion, 
  119.                   os.path.join('/usr/lib','python'+self.pyversion,'py_compile.py')]
  120.                  + self)
  121.         else:
  122.             for filename in self:
  123.                 debug("compile "+filename+'c')
  124.                 try:
  125.                     # Note that compile doesn't raise PyCompileError by default
  126.                     compile(filename, doraise=True)
  127.                 except IOError, (errno, strerror):
  128.                     warning("I/O error while trying to byte-compile %s (%s): %s" % (filename, errno, strerror))
  129.                 except PyCompileError, inst:
  130.                     warning("compile error while trying to byte-compile %s: %s" % (filename, inst.msg))
  131.                 except:
  132.                     warning("unexpected error while trying to byte-compile %s: %s" % (filename, sys.exc_info()[0]))
  133.     def clean(self):
  134.         for filename in self:
  135.             for ext in ['c', 'o']:
  136.                 fullpath=filename+ext
  137.                 if os.path.exists(fullpath):
  138.                     debug("remove "+fullpath)
  139.                     os.remove(fullpath)
  140.  
  141.  
  142. # Abstract class for PrivateFileList and SharedFileList
  143. class _FileList(list):
  144.     def __init__ (self, path):
  145.         self.name = path
  146.         for line in file(path):
  147.             line = line.strip()
  148.             if (not line) or line.startswith('#'):
  149.                 continue
  150.             if line.startswith('/'):
  151.                 self.append(line)
  152.                 continue
  153.             line = [x.strip() for x in line.split('=',1)]
  154.             if len(line) != 2:
  155.                 warning("Parse error in %s"%path)
  156.                 continue
  157.             self.parse_option(*line)
  158.  
  159. # This class represents a file list as provided in the /usr/share/python-support/$package.public
  160. # Useful for public modules and extensions
  161. class SharedFileList(_FileList, _PublicList):
  162.     def parse_option (self, arg, value):
  163.         if arg=='pyversions':
  164.             self.pyversions = pysupport.version_list(value)
  165.         # Ignore unknown arguments for extensivity
  166.  
  167. # This class represents a file list as provided in the /usr/share/python-support/$package.private
  168. # Useful for private modules
  169. class PrivateFileList(_FileList, _PrivateList):
  170.     def parse_option (self, arg, value):
  171.         if arg=='pyversion':
  172.             self.pyversion = value
  173.  
  174. # This is a helper generator that goes through files of interest in a given directory
  175. def allfiles(path, onlypy=False):
  176.     for root, dirs, files in os.walk(path):
  177.         for f in files:
  178.             if (onlypy and not f.endswith(".py")) or f== ".version":
  179.                 continue
  180.             yield os.path.join(root,f)
  181.         if not onlypy:
  182.             for d in dirs:
  183.                 d = os.path.join(root, d)
  184.                 if os.path.islink(d):
  185.                     yield d
  186.  
  187. # This class emulates the file listing as provided by /usr/share/python-support/$package.public
  188. # with the deprecated layout /usr/{lib,share}/python-support/$package/
  189. class SharedDirList(_PublicList):
  190.     def __init__ (self, path):
  191.         self.name = path
  192.         # Add all files to the file listing
  193.         self.extend(allfiles(path))
  194.         verfile=os.path.join(path,'.version')
  195.         extdir=path.replace(sourcepath,old_extensionpath,1)
  196.         if os.path.isfile(verfile):
  197.             # If we have a .version, use it
  198.             self.pyversions = pysupport.version_list(file(verfile).readline())
  199.         elif os.path.isdir(extdir):
  200.             # Try to obtain the list of supported versions
  201.             # from the extensions in /usr/lib
  202.             self.pyversions = isect(py_supported,os.listdir(extdir))
  203.         else:
  204.             # Otherwise, support all versions
  205.             pass
  206.  
  207.         if os.path.isdir(extdir):
  208.             # Add the extensions to the file listing
  209.             for version in self.pyversions:
  210.                 self.extend(allfiles(os.path.join(extdir,version)))
  211.  
  212. # This class emulates the file listing as provided by /usr/share/python-support/$package.private
  213. # with the deprecated layout /usr/share/python-support/$package.dirs
  214. class PrivateDirList(_PrivateList):
  215.     def __init__ (self, path):
  216.         self.name = path
  217.         self.extend(allfiles(path, onlypy=True))
  218.         versionfile = os.path.join(path, ".pyversion")
  219.         if os.path.isfile(versionfile):
  220.             self.pyversion = file(versionfile).readline().strip()
  221.  
  222.  
  223. class CachedFileList(dict):
  224.     def __getitem__ (self, name):
  225.         if name in self and dict.__getitem__(self, name) == None:
  226.             if name.startswith("/"):
  227.                 # The case of old-style private directories
  228.                 self[name] = PrivateDirList (name)
  229.             else:
  230.                 path = os.path.join (sourcepath, name)
  231.                 if name.endswith(".public"):
  232.                     self[name] = SharedFileList (path)
  233.                 elif name.endswith(".private"):
  234.                     self[name] = PrivateFileList (path)
  235.                 elif os.path.isdir(path):
  236.                     self[name] = SharedDirList (path)
  237.                 else:
  238.                     raise Exception("[Internal Error] I don't know what to do with this path: %s"%path)
  239.         return dict.__getitem__(self, name)
  240.  
  241.  
  242. def bytecompile_all(py,path=None):
  243.     if not path:
  244.         path=os.path.join(basepath,py)
  245.     if not os.path.isdir(path):
  246.         return
  247.     debug("Byte-compilation of whole %s..."%path)
  248.     os.spawnl(os.P_WAIT, '/usr/bin/'+py, py,
  249.               os.path.join('/usr/lib/',py,'compileall.py'), '-q', path)
  250.  
  251. # A function to create the ".path" at the root of the installed directory
  252. # Returns the list of affected directories
  253. def create_dotpath(py):
  254.   path=os.path.join(basepath,py)
  255.   if not os.path.isdir(path):
  256.     return
  257.   pathfile=os.path.join(path,".path")
  258.   debug("Generation of %s..."%pathfile)
  259.   pathlist=[path]
  260.   ret=[]
  261.   for f in os.listdir(path):
  262.     f=os.path.join(path,f)
  263.     if f.endswith(".pth") and os.path.isfile(f):
  264.       for l in file(f):
  265.         l=l.rstrip('\n')
  266.         if l.startswith('import'):
  267.           # Do not ship lines starting with "import", they are executed! (complete WTF)
  268.           continue
  269.         pathlist.append(l)
  270.         l2=os.path.join(path,l)
  271.         pathlist.append(l2)
  272.         ret.append(l2)
  273.   fd=file(pathfile,"w")
  274.   fd.writelines([l+'\n' for l in pathlist])
  275.   fd.close()
  276.   return ret
  277.  
  278. def post_change_stuff(py):
  279.   # All the changes that need to be done after anything has changed
  280.   # in a /usr/lib/pymodules/pythonX.Y directory
  281.   # * Cleanup of all dangling symlinks that are left out after a package
  282.   #   is upgraded/removed.
  283.   # * The namespace packages are here because python doesn't consider a
  284.   #   directory to be able to contain packages if there is no __init__.py
  285.   #   file (yes, this is completely stupid).
  286.   # * The .path file must be created by concatenating all those .pth
  287.   #   files that extend sys.path (this also badly sucks).
  288.   # * Byte-compilation of all .py files that haven't already been
  289.   path=os.path.join(basepath,py)
  290.   if not os.path.isdir(path):
  291.     return
  292.   # First, remove any dangling symlinks.
  293.   # In the same loop, we find which directories may need a namespace package
  294.   dirhash={}
  295.   for dir, dirs, files in os.walk(path):
  296.     dirhash[dir]=False
  297.     files.sort() # We need the .py to appear before the .pyc
  298.     for f in files+dirs:
  299.       # We also examine dirs as some symlinks are dirs
  300.       abspath=os.path.join(dir,f)
  301.       islink=os.path.islink(abspath)
  302.       if islink:
  303.         if not os.path.exists(abspath):
  304.           # We refer to a file that was removed
  305.           debug("remove "+abspath)
  306.           os.remove(abspath)
  307.           continue
  308.         srcfile = os.readlink (abspath)
  309.         # Remove links left here after a change in the supported python versions for a package
  310.         removed = False
  311.         for package in public_packages:
  312.           if srcfile in public_packages[package]:
  313.             if py not in public_packages[package].pyversions:
  314.               debug("remove "+abspath)
  315.               os.remove(abspath)
  316.               removed = True
  317.             break
  318.         else:
  319.           # Remove files provided by packages that do not use python-support anymore
  320.           debug("remove "+abspath)
  321.           os.remove(abspath)
  322.           removed = True
  323.         if removed:
  324.           # Do not go further, the file was removed
  325.           continue
  326.       if f[-4:] in ['.pyc', '.pyo']:
  327.         if not os.path.exists(abspath[:-1]):
  328.           debug("remove "+abspath)
  329.           os.remove(abspath)
  330.           continue
  331.       elif f[-3:] in ['.py', '.so']:
  332.         if islink or f!='__init__.py':
  333.           # List the directory as maybe needing a namespace packages
  334.           d=dir
  335.           while dirhash.has_key(d) and not dirhash[d]:
  336.             dirhash[d]=True
  337.             d=os.path.dirname(d)
  338.     # Remove the directory if it is empty after our crazy removals
  339.     try:
  340.       os.removedirs(dir)
  341.     except OSError:
  342.       pass
  343.   dirhash[path]=False
  344.   # Then, find which directories belong in a .pth file
  345.   # These directories don't need a namespace package, so we
  346.   # set them to False in dirhash
  347.   for p in create_dotpath (py):
  348.     dirhash[p] = False
  349.   # Finally, create/remove namespace packages
  350.   for dir in dirhash:
  351.     initfile=os.path.join(dir,"__init__.py")
  352.     noinitfile=os.path.join(dir,".noinit")
  353.     if dirhash[dir] and not os.path.exists(noinitfile):
  354.       if not os.path.exists(initfile):
  355.         debug("create namespace "+initfile)
  356.         file(initfile,"w").close()
  357.     else:
  358.       for e in ['','c','o']:
  359.         if os.path.exists(initfile+e):
  360.           debug('remove namespace '+initfile+e)
  361.           os.remove(initfile+e)
  362.       try:
  363.         os.removedirs(dir)
  364.       except OSError:
  365.         pass
  366.   bytecompile_all(py)
  367.  
  368.  
  369. # A helper function for older $package.dirs files
  370. def dirlist_file(f):
  371.     return [ l.rstrip('\n') for l in file(f) if len(l)>1 ]
  372.  
  373. # End of function definitions - Start of the script itself
  374.  
  375. # Ensure that the umask is sane
  376. os.umask(022)
  377.  
  378. # Read all modules listing
  379. public_packages = CachedFileList()
  380. private_packages = CachedFileList()
  381. dirlisting = os.listdir(sourcepath)
  382. for name in dirlisting:
  383.     path=os.path.join(sourcepath,name)
  384.     if name == "private":
  385.         continue
  386.     ext = name.split(".")[-1]
  387.     if os.path.isdir(path):
  388.         if ext in ["public", "private", "dirs"]:
  389.             # Presumably a bogus directory, see #528130
  390.             warning("%s is a directory"%name)
  391.         else:
  392.             public_packages[name] = None
  393.         continue
  394.     if not os.path.isfile(path):
  395.         # Ignore whatever is not a file, like dangling symlinks
  396.         continue
  397.     if ext == "public":
  398.         public_packages[name] = None
  399.     elif ext == "private":
  400.         private_packages[name] = None
  401.     elif ext == "dirs":
  402.         for dirname in dirlist_file (path):
  403.             private_packages[dirname] = None
  404.     # Just ignore all other files
  405.  
  406. # Parse arguments
  407. do_public=[]
  408. do_private=[]
  409. for arg in args:
  410.     if arg.startswith(sourcepath):
  411.         arg = arg[len(sourcepath):].lstrip("/")
  412.     if arg.endswith(".dirs") and arg in dirlisting:
  413.         for dirname in dirlist_file(os.path.join(sourcepath, arg)):
  414.             do_private.append(private_packages[dirname])
  415.     elif arg in public_packages:
  416.         do_public.append(public_packages[arg])
  417.     elif arg in private_packages:
  418.         do_private.append(private_packages[arg])
  419.     else:
  420.         if options.clean_mode:
  421.             warning("%s does not exist.\n         Some bytecompiled files may be left behind."%arg)
  422.         else:
  423.             parser.error("%s is not a recognized python-support module."%arg)
  424.  
  425. # Check consistency options (although these ones should not exist anymore)
  426. if do_private and options.force_public:
  427.     parser.error("Option -i cannot be used with a .private module file.")
  428. if do_public and options.force_private:
  429.     parser.error("Option -b cannot be used with a .public module file.")
  430.  
  431. if options.rebuild_everything:
  432.     options.rebuild_all = True
  433.     for pyver in py_supported:
  434.         dir = os.path.join(basepath,pyver)
  435.         if os.path.isdir(dir):
  436.             shutil.rmtree(dir)
  437.  
  438. # Check for changes in installed python versions
  439. need_postinstall = []
  440. for pyver in py_oldversions+py_supported:
  441.     dir = os.path.join(basepath,pyver)
  442.     # Check for ".path" because sometimes the directory already exists 
  443.     # while the python version isn't installed, because of some .so's.
  444.     if pyver not in py_installed and os.path.isdir(dir):
  445.         debug("Removing obsolete directory %s..."%(dir))
  446.         shutil.rmtree(dir)
  447.     if pyver in py_installed and not os.path.isfile(os.path.join(dir,".path")):
  448.         need_postinstall.append(pyver)
  449. if need_postinstall:
  450.     debug("Building all modules for %s..."%(" ".join(need_postinstall)))
  451.     for package in public_packages:
  452.         public_packages[package].install(need_postinstall)
  453.     for pyver in need_postinstall:
  454.         # Here we need to launch create_dotpath because otherwise we could
  455.         # end up without the .path file that is checked 6 lines earlier
  456.         create_dotpath(pyver)
  457.  
  458. if options.rebuild_all:
  459.     for package in private_packages:
  460.         private_packages[package].bytecompile()
  461.  
  462.  
  463. # Now for the processing of what was handed on the command line
  464. for package in do_private:
  465.     if not options.clean_mode:
  466.         package.bytecompile()
  467.     else:
  468.         package.clean()
  469.  
  470. need_dotpath = False
  471. for package in do_public:
  472.     need_postinstall = concat (need_postinstall, isect(package.pyversions,py_installed))
  473.     if options.clean_mode:
  474.         continue
  475.     package.install(py_installed)
  476.     for f in package:
  477.         if f.endswith(".pth"):
  478.             need_dotpath = True
  479.  
  480. # Only do the funny and time-consuming things when the -p option is
  481. # given, e.g when python-support is triggered.
  482. if need_postinstall and 'DPKG_RUNNING_VERSION' in os.environ and not options.post_install:
  483.     ret = os.spawnlp(os.P_WAIT, 'dpkg-trigger', 'dpkg-trigger', '--no-await', 'pysupport')
  484.     if ret:
  485.         sys.stderr.write("ERROR: dpkg-trigger failed\n")
  486.         sys.exit(1)
  487.     if need_dotpath:
  488.         for py in need_postinstall:
  489.             create_dotpath (py)
  490.     need_postinstall = []
  491.  
  492. if options.post_install:
  493.     # The trigger has been activated; do it for all installed versions
  494.     need_postinstall = py_installed
  495. if need_postinstall:
  496.     need_dotpath = False
  497.     for py in need_postinstall:
  498.         post_change_stuff(py)
  499.  
  500.